#include "Grid.h"
#include <cassert>
#include <memory>
#include <cassert>
#include <iostream>
#include "ray.h"
#include "Utility.h"
#include "Mesh.h"
#include "Object.h"

mray::Grid::Grid()
{
	m_cellLen = 0.f;
	memset(m_nGrids, 0, sizeof(m_nGrids));
	m_pCells = nullptr;
}

mray::Grid::~Grid()
{
	if(m_pCells) delete [] m_pCells;
}

void mray::Grid::Build(const std::vector<Collible*>& collibles)
{
	assert(collibles.size());
	std::vector<AABB> boxes;
	boxes.resize(collibles.size());

	for(size_t i = 0; i < collibles.size(); i++){
		collibles[i]->GetAABB(boxes[i]);
		m_box &= boxes[i];
	}

	vec3 tmp = m_box.m_max - m_box.m_min;
	m_cellLen = std::pow(tmp.x*tmp.y*tmp.z/collibles.size(), 1.f/3.f)*0.5f;
	tmp /= m_cellLen;
	for(uint i = 0; i < 3; i++)	m_nGrids[i] = (uint)(tmp[i] + 1);

	m_pCells = new Cell[m_nGrids[0]*m_nGrids[1]*m_nGrids[2]];
	//process every bbox
	for(size_t i = 0; i < boxes.size(); i++){
		uint minIndices[3], maxIndices[3];
		vec3 minVec = boxes[i].m_min - m_box.m_min;
		vec3 maxVec = boxes[i].m_max - m_box.m_min;
		for(uint j = 0; j < 3; j++){
			minIndices[j] = RoundClamp(minVec[j]/m_cellLen, 0, m_nGrids[j]-1);
			maxIndices[j] = RoundClamp(maxVec[j]/m_cellLen, 0, m_nGrids[j]-1);
		}
		for(uint x = minIndices[0]; x <= maxIndices[0]; x++)
			for(uint y = minIndices[1]; y <= maxIndices[1]; y++)
				for(uint z = minIndices[2]; z <= maxIndices[2]; z++)
					m_pCells[GetIndex(x,y,z)].Append(new Cell(collibles[i]));
	}
}

bool mray::Grid::Hit(Ray& ray, Result* pResult) const
{
	//when camera locate on the surface of the scene bounding box
	//it can neithor be contained by the box
	//nor be hit by the box
	//so the ray ignore the scene box
	//here i fix it by accept it when it on the surface

	uint coords[3];
	vec3 tmp;
	if(m_box.Contain(ray.m_ori)){
		tmp = ray.m_ori - m_box.m_min;
	}else{
		if(m_box.Hit(ray)){
			tmp = ray.GetPoint() - m_box.m_min;
			ray.m_mint = FLT_MAX;//for testing objects after
		}else{
			return false;
		}
	}
	//calcualte current coords
	for(uint i = 0; i < 3; i++)
		coords[i] = RoundClamp(tmp[i]/m_cellLen, 0, m_nGrids[i]-1);

	vec3 tNext, tStep;
	for(uint i = 0; i < 3; i++){
		switch(Sign(ray.m_dir[i])){
		case -1:
			tNext[i] = (m_box.m_min[i]+coords[i]*m_cellLen-ray.m_ori[i])/ray.m_dir[i];
			break;
		case 0:
			tNext[i] = FLT_MAX;
			break;
		case 1:
			tNext[i] = (m_box.m_min[i]+(coords[i]+1)*m_cellLen-ray.m_ori[i])/ray.m_dir[i];
			break;
		}
		tStep[i] = m_cellLen/abs(ray.m_dir[i]);
	}

	while(true){
		//trace the cell
		Cell* pCell = &(m_pCells[GetIndex(coords[0], coords[1], coords[2])]);
		bool ret = false;
		while(pCell && pCell->m_pObject){
			ret |= pCell->m_pObject->Hit(ray, pResult);
			pCell = pCell->m_next;
		}
		AXIS axis = tNext.GetMinAxis();
		if( ret && ray.m_mint < tNext[axis]) return true;
		//if there is collision in this cell, we can judge it
		coords[axis] += Sign(ray.m_dir[axis]);
		tNext[axis] += tStep[axis];
		if(coords[axis] >= m_nGrids[axis] ) return false;
	}
}

void mray::Grid::Print() const
{
	printf("nGrids x:%d y:%d z:%d\n", m_nGrids[0], m_nGrids[1], m_nGrids[2]);
	for(uint y = 0; y < m_nGrids[1]; y++){
		printf("y=%d\n",y);
		for(uint z = 0; z < m_nGrids[2]; z++){
			for(uint x = 0; x < m_nGrids[0]; x++){
				uint count = 0;
				Cell* pCell = &(m_pCells[GetIndex(x,y,z)]);
				while(pCell && pCell->m_pObject){
					count++;
					pCell = pCell->m_next;
				}
				printf("%d", count);
			}
			printf("\n");
		}
	}
}